﻿using Fast.ExceptionHandling;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.JsonWebTokens;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Fast.Extensions.Authentication.JwtBearer
{
    /// <summary>
    /// 
    /// </summary>
    public class JwtTokenService : ITokenService
    {
        /// <summary>
        /// Jwt参数
        /// </summary>
        private readonly JwtParametersOptions _jwtParameters;

        /// <summary>
        /// Token验证参数
        /// </summary>
        public TokenValidationParameters TokenValidationParameters { get; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="options"></param>
        /// <exception cref="ArgumentNullException"></exception>
        public JwtTokenService(IOptions<JwtParametersOptions> options)
        {
            _jwtParameters = options?.Value ?? throw new ArgumentNullException(nameof(JwtParametersOptions));
            TokenValidationParameters = new TokenValidationParameters
            {
                // 验证签发方密钥
                ValidateIssuerSigningKey = _jwtParameters.ValidateIssuerSigningKey.Value,
                // 签发方密钥
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtParameters.IssuerSigningKey)),
                // 验证签发方
                ValidateIssuer = _jwtParameters.ValidateIssuer.Value,
                // 设置签发方
                ValidIssuer = _jwtParameters.ValidIssuer,
                // 验证签收方
                ValidateAudience = _jwtParameters.ValidateAudience.Value,
                // 设置接收方
                ValidAudience = _jwtParameters.ValidAudience,
                // 验证生存期
                ValidateLifetime = _jwtParameters.ValidateLifetime.Value,
                // 过期时间容错值
                ClockSkew = TimeSpan.FromSeconds(_jwtParameters.ClockSkew.Value),
            };
        }

        /// <summary>
        /// 获取Token
        /// </summary>
        /// <param name="jwtPayload"></param>
        /// <param name="expiredTime"></param>
        /// <returns></returns>
        public async Task<TokenEntity> TokenAsync(JwtPayload jwtPayload, long? expiredTime = null)
        {
            return await BuildTokenEntityAsync(jwtPayload, expiredTime);
        }

        /// <summary>
        /// 验证 Token
        /// </summary>
        /// <param name="token"></param>
        /// <returns></returns>
        public async Task<TokenValidationResult> ValidateTokenAsync(string token)
        {
            var jsonWebTokenHandler = new JsonWebTokenHandler();
            return await Task.Run(() => jsonWebTokenHandler.ValidateToken(token, TokenValidationParameters));
        }

        /// <summary>
        /// 刷新Token
        /// </summary>
        /// <param name="refreshToken"></param>
        /// <returns></returns>
        public async Task<TokenEntity> RefreshTokenAsync(string refreshToken)
        {
            var jsonWebTokenHandler = new JsonWebTokenHandler();
            var refreshTokenValidateResult = jsonWebTokenHandler.ValidateToken(refreshToken, TokenValidationParameters);
            if (!refreshTokenValidateResult.IsValid)
            {
                throw new FastException("刷新Token验证失败");
            }
            var jwtPayload = new JwtPayload();
            refreshTokenValidateResult.ClaimsIdentity.Claims.ToList().ForEach(claim =>
            {
                jwtPayload.Add(claim.Type, claim.Value);
            });
            return await BuildTokenEntityAsync(jwtPayload);
        }

        /// <summary>
        /// 解密
        /// </summary>
        /// <param name="token"></param>
        /// <param name="stringValueClaimTypes"></param>
        /// <param name="listValueClaimTypes"></param>
        /// <returns></returns>
        public async Task<JwtPayload> GetJwtPayloadAsync(string token, List<string> stringValueClaimTypes = null, List<string> listValueClaimTypes = null)
        {
            return await Task.Run(() =>
            {
                stringValueClaimTypes ??= new List<string>();
                listValueClaimTypes ??= new List<string>();

                var tokenHandler = new JsonWebTokenHandler();
                var jwtToken = tokenHandler.ReadJsonWebToken(token);
                var jwtPayload = new JwtPayload();
                foreach (var claimType in stringValueClaimTypes)
                {
                    var claim = jwtToken.Claims.FirstOrDefault(p => p.Type == claimType);
                    if (claim != null)
                    {
                        jwtPayload.Add(claimType, claim.Value);
                    }
                }
                foreach (var claimType in listValueClaimTypes)
                {
                    var claims = jwtToken.Claims.Where(p => p.Type == claimType).Select(p => p.Value).ToList();
                    if (claims.Any())
                    {
                        jwtPayload.Add(claimType, claims);
                    }
                }
                return jwtPayload;
            });
        }

        /// <summary>
        /// 构建刷新 Token
        /// </summary>
        /// <param name="jwtPayload"></param>
        /// <returns></returns>
        protected virtual Task<string> BuildRefreshTokenAsync(JwtPayload jwtPayload)
            => Task.FromResult(GenerateTtoken(jwtPayload, _jwtParameters.RefreshTokenExpiredTime));

        #region Private Methods

        /// <summary>
        /// 构建 TokenEntity
        /// </summary>
        /// <param name="jwtPayload"></param>
        /// <param name="expiredTime"></param>
        /// <returns></returns>
        private async Task<TokenEntity> BuildTokenEntityAsync(JwtPayload jwtPayload, long? expiredTime = null)
        {
            var accessToken = GenerateTtoken(jwtPayload, expiredTime);
            var expiresIn = (expiredTime ?? _jwtParameters.ExpiredTime.Value) * 60;
            var tokenEntity = new TokenEntity { AccessToken = accessToken, ExpiresIn = expiresIn };
            if (_jwtParameters.EnableRefreshToken.Value)
            {
                jwtPayload.Remove(JwtRegisteredClaimNames.Exp);
                var refreshTokenOwnerKey = "x-rt-owner";
                if (!jwtPayload.ContainsKey(refreshTokenOwnerKey))
                {
                    jwtPayload.Add(refreshTokenOwnerKey, jwtPayload[JwtRegisteredClaimNames.Jti]);
                }
                jwtPayload.Remove(JwtRegisteredClaimNames.Jti);
                tokenEntity.RefreshToken = await BuildRefreshTokenAsync(jwtPayload);
            }
            return tokenEntity;
        }

        /// <summary>
        /// 生成 Token
        /// </summary>
        /// <param name="payload"></param>
        /// <param name="expiredTime"></param>
        /// <returns></returns>
        private string GenerateTtoken(JwtPayload payload, long? expiredTime = null)
        {
            payload ??= new JwtPayload();
            BuildPayload(payload, expiredTime);
            return GenerateTtoken(JsonConvert.SerializeObject(payload));
        }

        /// <summary>
        /// 生成 Token
        /// </summary>
        /// <param name="payload"></param>
        /// <returns></returns>
        private string GenerateTtoken(string payload)
        {
            var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtParameters.IssuerSigningKey));
            var credentials = new SigningCredentials(securityKey, _jwtParameters.Algorithm);
            var jsonWebTokenHandler = new JsonWebTokenHandler();
            return jsonWebTokenHandler.CreateToken(payload, credentials);
        }

        /// <summary>
        /// 构建payload
        /// </summary>
        /// <param name="payload"></param>
        /// <param name="expiredTime"></param>
        private void BuildPayload(JwtPayload payload, long? expiredTime = null)
        {
            var datetimeOffset = DateTimeOffset.UtcNow;

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Jti))
            {
                payload.Add(JwtRegisteredClaimNames.Jti, Guid.NewGuid());
            }

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Iat))
            {
                payload.Add(JwtRegisteredClaimNames.Iat, datetimeOffset.ToUnixTimeSeconds());
            }

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Nbf))
            {
                payload.Add(JwtRegisteredClaimNames.Nbf, datetimeOffset.ToUnixTimeSeconds());
            }

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Exp))
            {
                var minute = expiredTime ?? _jwtParameters!.ExpiredTime.Value;
                payload.Add(JwtRegisteredClaimNames.Exp, datetimeOffset.AddMinutes(minute).ToUnixTimeSeconds());
            }

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Iss))
            {
                payload.Add(JwtRegisteredClaimNames.Iss, _jwtParameters.ValidIssuer);
            }

            if (!payload.ContainsKey(JwtRegisteredClaimNames.Aud))
            {
                payload.Add(JwtRegisteredClaimNames.Aud, _jwtParameters.ValidAudience);
            }
        }

        #endregion
    }
}
